package bma.common.json;

import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Stack;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;

import bma.common.langutil.core.ExceptionUtil;
import bma.common.langutil.core.ObjectUtil;
import bma.common.langutil.core.StringUtil;
import bma.common.langutil.core.ValueUtil;

public class JsonEditor {

	protected static class StackItem {
		public String path;
		public JsonNode node;

		public static StackItem create(String path, JsonNode node) {
			StackItem r = new StackItem();
			r.path = path;
			r.node = node;
			return r;
		}
	}

	private ObjectMapper mapper;

	private JsonNode rootObject;
	private StackItem current;
	private Stack<StackItem> stack;

	public JsonEditor(JsonNode rootObject, ObjectMapper mapper) {
		super();
		this.rootObject = rootObject;
		this.current = StackItem.create("", rootObject);
		stack = new Stack<StackItem>();
		this.mapper = mapper;
	}

	public ObjectMapper getMapper() {
		return mapper;
	}

	public void setMapper(ObjectMapper mapper) {
		this.mapper = mapper;
	}

	public Object getRootObject() {
		return rootObject;
	}

	public static JsonEditor start() {
		return start(null);
	}

	public static JsonEditor start(String json) {
		return start(json, JsonUtil.getDefaultMapper());
	}

	public static JsonEditor start(String json, ObjectMapper mapper) {
		try {
			if (ValueUtil.empty(json)) {
				json = "{}";
			}
			JsonNode node = mapper.readValue(json, JsonNode.class);
			return start(node, mapper);
		} catch (Exception e) {
			throw ExceptionUtil.throwRuntime(e);
		}
	}

	public static JsonEditor start(JsonNode root, ObjectMapper mapper) {
		return new JsonEditor(root, mapper);
	}

	public JsonNode toNode(JsonNode node) {
		if (node instanceof JEVNode) {
			JEVNode v = (JEVNode) node;
			return v.getNode();
		}
		return node;
	}

	public ObjectNode toObject(JsonNode cur) {
		if (cur instanceof JEVNode) {
			JEVNode v = (JEVNode) cur;
			if (v.isUnknow()) {
				cur = mapper.createObjectNode();
				v.setNode(cur);
			} else {
				cur = v.getNode();
			}
		}
		if (cur instanceof ObjectNode) {
			return (ObjectNode) cur;
		}
		return null;
	}

	public ArrayNode toArray(JsonNode cur) {
		if (cur instanceof JEVNode) {
			JEVNode unknow = (JEVNode) cur;
			if (unknow.isUnknow()) {
				cur = mapper.createArrayNode();
				unknow.setNode(cur);
			} else {
				cur = unknow.getNode();
			}
		}
		if (cur instanceof ArrayNode) {
			return (ArrayNode) cur;
		}
		return null;
	}

	public static Object arrayRef(String nodeId) {
		if (ValueUtil.empty(nodeId))
			return null;
		if (nodeId.startsWith("[") && nodeId.endsWith("]")) {
			String v = nodeId.substring(1, nodeId.length() - 1);
			if (v.isEmpty())
				return -1;
			int iv = ValueUtil.intValue(v, Integer.MIN_VALUE);
			if (iv != Integer.MIN_VALUE) {
				return iv;
			}
			return v;
		}
		return null;
	}

	protected int arrayIdx(ArrayNode cur, Object ref) {
		if (ref instanceof Integer) {
			return (Integer) ref;
		}
		if (ref instanceof String) {
			String sref = (String) ref;
			String[] ns = StringUtil.tokenSplit(sref, "=");
			if (ns.length > 1) {
				String name = ns[0];
				String match = ns[1];
				int c = cur.size();
				for (int i = 0; i < c; i++) {
					JsonNode v = cur.get(i);
					if (v == null)
						continue;
					ObjectNode ov = toObject(v);
					Object fv = ov.get(name);
					if (fv == null)
						continue;
					if (StringUtil.equals(match, fv.toString())) {
						return i;
					}
				}
			}
			return -1;
		}
		throw new IllegalArgumentException("unknow array ref [" + ref + "]");
	}

	protected JsonNode location(JsonNode cur, String nodeId) {
		Object ref = arrayRef(nodeId);

		if (ref == null) {
			// object
			ObjectNode onode = toObject(cur);
			if (onode != null) {
				JsonNode v = onode.get(nodeId);
				if (v == null) {
					return new JEVNode(nodeId);
				}
				return v;
			} else {
				throw new IllegalArgumentException("invalid JsonObject(" + cur
						+ ")");
			}
		} else {
			ArrayNode anode = toArray(cur);
			if (anode != null) {
				int idx = arrayIdx(anode, ref);
				JsonNode v = anode.get(idx);
				if (v == null) {
					return new JEVNode(idx);
				}
				return v;
			}
			throw new IllegalArgumentException("invalid JsonArray(" + cur + ")");
		}
	}

	protected void merge(JsonNode parent, JsonNode val) {
		if (val instanceof JEVNode) {
			JEVNode vnode = (JEVNode) val;
			if (((JEVNode) val).isUnknow()) {
				return;
			}

			if (vnode.getField() != null) {
				ObjectNode p = toObject(parent);
				if (p == null) {
					throw new IllegalArgumentException("invalid JsonObject["
							+ parent + "]");
				}
				p.put(vnode.getField(), vnode.getNode());
			} else {
				ArrayNode p = toArray(parent);
				if (p == null) {
					throw new IllegalArgumentException("invalid JsonArray["
							+ parent + "]");
				}
				if (vnode.getIndex() >= 0 && vnode.getIndex() < p.size()) {
					p.set(vnode.getIndex(), vnode.getNode());
				} else {
					p.add(vnode.getNode());
				}
			}
		}
	}

	/**
	 * 把当前的节点(C)压入堆栈，并设置C:path为当前节点
	 * 
	 * @param path
	 * @return
	 */
	public JsonEditor pushObject(String path) {
		return push(path, true, false);
	}

	public JsonEditor pushArray(String path) {
		return push(path, false, true);
	}

	public JsonEditor push(String path) {
		return push(path, false, false);
	}

	protected JsonEditor push(String path, boolean obj, boolean arr) {

		String[] ns = pathArray(path);
		for (String n : ns) {
			stack.push(current);
			JsonNode o = location(current.node, n);
			if (o == null) {
				throw new IllegalArgumentException(currentPath() + ":" + n
						+ " is invalid");
			}
			StackItem item = StackItem.create(path, o);
			current = item;
		}
		if (obj) {
			if (toObject(current.node) == null) {
				throw new IllegalArgumentException(currentPath()
						+ " invalid JsonObject");
			}
		}
		if (arr) {
			if (toArray(current.node) == null) {
				throw new IllegalArgumentException(currentPath()
						+ " invalid JsonArray");
			}
		}
		return this;
	}

	private String[] pathArray(String path) {
		return StringUtil.tokenSplit(path, ".");
	}

	/**
	 * 从堆栈中弹出节点，设置为当前节点
	 * 
	 * @return
	 */
	public JsonEditor pop() {
		if (stack.isEmpty())
			throw new IllegalArgumentException("can't pop last element");

		StackItem old = current;
		current = stack.pop();
		// build up VJsonNode
		merge(current.node, old.node);
		return this;
	}

	public boolean isRoot() {
		return stack.isEmpty();
	}

	public JsonNode current() {
		JsonNode v = current.node;
		if (v instanceof JEVNode) {
			JEVNode n = (JEVNode) v;
			return n.getNode();
		}
		return v;
	}

	public String path(String[] ns, int end, boolean cur) {
		StringBuffer buf = new StringBuffer(128);
		for (int i = 0; i < end; i++) {
			if (buf.length() > 0)
				buf.append(".");
			buf.append(ns[i]);
		}
		String r = buf.toString();
		String c = "";
		if (!cur) {
			return r;
		}
		c = currentPath();
		if (r.length() > 0 && c.length() > 0) {
			return c + "." + r;
		}
		return c + r;

	}

	public String currentPath() {
		StringBuffer buf = new StringBuffer(128);
		for (StackItem item : stack) {
			if (buf.length() > 0)
				buf.append(".");
			buf.append(item.path);
		}
		if (buf.length() > 0)
			buf.append(".");
		buf.append(current.path);
		return buf.toString();
	}

	/**
	 * 设置一个子节点的数据
	 * 
	 * @param key
	 * @param val
	 * @return
	 */
	public JsonEditor setNode(String path, Object val) {
		String[] ns = pathArray(path);
		JsonNode p = current.node;
		setNode(p, ns, 0, val);
		return this;
	}

	protected void setNode(JsonNode p, String[] ns, int idx, Object val) {
		String n = ns[idx];
		if (idx < ns.length - 1) {
			JsonNode o = location(p, n);
			if (o == null) {
				throw new IllegalArgumentException(path(ns, idx + 1, true)
						+ " is invalid");
			}
			setNode(o, ns, idx + 1, val);
			merge(p, o);
			return;
		}
		Object ref = arrayRef(n);
		if (ref == null) {
			ObjectNode op = toObject(p);
			if (op == null) {
				throw new IllegalArgumentException(path(ns, idx, true)
						+ " invalid JsonObject");
			}
			op.put(n, mapper.convertValue(val, JsonNode.class));
			return;
		} else {
			ArrayNode ap = toArray(p);
			if (ap == null) {
				throw new IllegalArgumentException(path(ns, idx, true)
						+ " invalid JsonArray");
			}
			JsonNode value = mapper.convertValue(val, JsonNode.class);
			int aidx = arrayIdx(ap, ref);
			if (aidx >= 0 && aidx < ap.size()) {
				ap.set(aidx, value);
			} else {
				ap.add(value);
			}
			return;
		}
	}

	public JsonEditor setField(String name, Object val) {
		ObjectNode op = toObject(current.node);
		if (op == null) {
			throw new IllegalArgumentException(currentPath()
					+ " invalid JsonObject");
		}
		op.put(name, mapper.convertValue(val, JsonNode.class));
		return this;
	}

	public JsonEditor setIndex(int idx, Object val) {
		ArrayNode ap = toArray(current.node);
		if (ap == null) {
			throw new IllegalArgumentException(currentPath()
					+ " invalid JsonArray");
		}
		JsonNode v = mapper.convertValue(val, JsonNode.class);
		if (idx < ap.size()) {
			ap.set(idx, v);
		} else {
			ap.add(v);
		}
		return this;
	}

	/**
	 * 在当前的节点增加数据
	 * 
	 * @param key
	 * @param val
	 * @return
	 */
	public JsonEditor addObject(int idx) {
		return add(idx, mapper.createObjectNode());
	}

	public JsonEditor add(int idx, Object val) {
		ArrayNode ap = toArray(current.node);
		if (ap == null) {
			throw new IllegalArgumentException(currentPath()
					+ " invalid JsonArray");
		}
		ap.insert(idx, mapper.convertValue(val, JsonNode.class));
		return this;
	}

	/**
	 * 删除当前的节点
	 * 
	 * @return
	 */
	public JsonEditor remove() {
		StackItem p = stack.isEmpty() ? null : stack.peek();
		if (p == null) {
			throw new IllegalArgumentException("can't remove root object");
		}
		JsonNode c = current.node;
		pop();
		if (!(c instanceof JEVNode)) {
			JsonNode node = toNode(p.node);
			if (node != null) {
				remove(toNode(p.node), c);
			}
		}
		return this;
	}

	protected boolean remove(JsonNode cur, JsonNode node) {
		boolean r = false;
		if (cur instanceof ObjectNode) {
			ObjectNode o = (ObjectNode) cur;
			Iterator<Entry<String, JsonNode>> it = o.getFields();
			while (it.hasNext()) {
				JsonNode n = it.next().getValue();
				if (ObjectUtil.equals(n, node)) {
					it.remove();
					r = true;
				}
			}
			return r;
		} else if (cur instanceof ArrayNode) {
			ArrayNode o = (ArrayNode) cur;
			Iterator<JsonNode> it = o.getElements();
			while (it.hasNext()) {
				JsonNode n = it.next();
				if (ObjectUtil.equals(n, node)) {
					it.remove();
					r = true;
				}
			}
		}
		return r;
	}

	/**
	 * 删除当前的节点
	 * 
	 * @return
	 */
	public JsonEditor removeNode(String path) {
		String[] ns = pathArray(path);
		JsonNode p = current.node;
		removeNode(p, ns, 0);
		return this;
	}

	protected void removeNode(JsonNode p, String[] ns, int idx) {
		String n = ns[idx];
		if (idx < ns.length - 1) {
			JsonNode o = location(p, n);
			if (o == null) {
				throw new IllegalArgumentException(path(ns, idx + 1, true)
						+ " is invalid");
			}
			removeNode(o, ns, idx + 1);
			return;
		}
		Object ref = arrayRef(n);
		if (ref == null) {
			ObjectNode op = toObject(p);
			if (op == null) {
				throw new IllegalArgumentException(path(ns, idx, true)
						+ " invalid JsonObject");
			}
			op.remove(n);
			return;
		} else {
			ArrayNode ap = toArray(p);
			if (ap == null) {
				throw new IllegalArgumentException(path(ns, idx, true)
						+ " invalid JsonArray");
			}
			int aidx = arrayIdx(ap, ref);
			ap.remove(aidx);
		}
	}

	public boolean exists(String path) {
		String[] ns = pathArray(path);
		JsonNode cur = current.node;
		for (int i = 0; i < ns.length; i++) {
			JsonNode node = location(cur, ns[i]);
			if (node instanceof JEVNode) {
				JEVNode vnode = (JEVNode) node;
				if (vnode.isUnknow())
					return false;
			}
			cur = node;
		}
		return true;
	}

	/**
	 * 结束编辑
	 */
	public JsonEditor end() {
		while (!stack.isEmpty()) {
			pop();
		}
		return this;
	}

	@Override
	public String toString() {
		return "JsonEditor[" + currentPath() + "]";
	}

	public Object mark() {
		return current;
	}

	public void reset(Object flag) {
		if (ObjectUtil.equals(current, flag)) {
			return;
		}
		if (stack.indexOf(flag) == -1) {
			throw new IllegalArgumentException("invalid mark");
		}
		for (;;) {
			pop();
			if (ObjectUtil.equals(current, flag))
				return;
		}
	}

	public void execute(String jsonOfCommands) {
		JsonEditCommand cmd = new JsonEditCommand();
		cmd.setCommandString(jsonOfCommands);
		cmd.execute(this);
	}
}
